说明:以下内容针对VC6环境
虽然利用DriverStudio的工程向导为我们生成了测试程序示例,但它大量使用了DriverStudio自己的封装库,现在来介绍在不使用DriverStudio封装库的情况下如何编写测试程序。
1、打开设备的两种方式
我们知道打开设备用CreateFile函数,其第一个参数应指向欲打开的USB设备,它可以是Device Interface,也可以是Device Symbolic Link(设备连接符或叫设备名)。要么用Interface打开USB设备,要么用Symbolic Link打开(不知正确与否),在利用DriverStudio工程向导生成时USB驱动程序的过程中可选择应用程序打开此USB设备的方式(见下图),在这时就确定了将来该用何种方式来打开设备。
1)用Interface方式打开设备
首先取得设备的GUID(什么是GUID?请看这篇文章:USB编程之二(常见设备类型的GUID)),然后利用设备GUID来得到设备Interface,最后就可以用CreateFile函数来打开设备了。
GUID由驱动程序定义,如上一节的驱动程序实例中对GUID的定义如下:
#define Easy_USB_51_ProgramerDevice_CLASS_GUID \
{ 0x47589a1e, 0xad02, 0x455e, { 0xa9, 0xf7, 0xcc, 0xb2, 0xd5, 0xe, 0x68, 0x54 } }
还可以利用第三方工具取得设备GUID,如USBView,还有DriverStudio自带的Symbolic Viewer),下面是用Symbolic Viewer查得的上一节驱动实例的GUID
我们这里利用DEFINE_GUID宏来定义设备GUID,内容如下:
DEFINE_GUID(Easy_USB_51_ProgramerDevice_CLASS_GUID,
0xC713541C, 0x3C65, 0x474A, 0x8E, 0xBC, 0x25, 0x87, 0x51, 0x49, 0xD2, 0x05);
DEFINE_GUID宏在initguid.h中定义的,所以需要在加入对此文件的引用:
另外,我们将要用到的一些函数需要setupapi.h和setupapi.lib,它们是VC6自带的,所以需要添加对这两个文件的引用:
①#include "Setupapi.h"
②选择菜单“Project”->“Settings”,切换到“Link”页,在“Object/library modules”中填入:setupapi.lib
好了,现在贴出以Interface方式打开设备的代码:
void CWR_CtrlLEDDlg::OnOpendevByInterface()
{
LONG ii = 0;
HDEVINFO hDeviceInfo;
DWORD bufferSize;
SP_DEVICE_INTERFACE_DATA interfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceDetail;
hDeviceInfo = SetupDiGetClassDevs(
(LPGUID)&Easy_USB_51_ProgramerDevice_CLASS_GUID,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
);
if (hDeviceInfo == INVALID_HANDLE_VALUE)
{
AfxMessageBox( "SetupDiGetClassDevs failed" );
return ;
}
interfaceData.cbSize = sizeof (interfaceData);
for (ii = 0;
SetupDiEnumDeviceInterfaces(
hDeviceInfo,
NULL,
(LPGUID)&Easy_USB_51_ProgramerDevice_CLASS_GUID,
ii,
&interfaceData);
++ii)
{
if (!SetupDiGetDeviceInterfaceDetail(
hDeviceInfo,
&interfaceData,
NULL,
0,
&bufferSize,
NULL))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
TRACE( "Error: couldn't get interface detail\n" );
continue ;
}
}
deviceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(bufferSize);
if (deviceDetail == NULL)
{
TRACE( "Error: Buffer allocation failed\n" );
continue ;
}
deviceDetail->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(
hDeviceInfo,
&interfaceData,
deviceDetail,
bufferSize,
NULL,
NULL))
{
TRACE( "Error: SetupDiGetDeviceInterfaceDetail failed\n" );
free(deviceDetail);
continue ;
}
SetupDiDestroyDeviceInfoList(hDeviceInfo);
m_hDev = CreateFile(
deviceDetail->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL
);
free(deviceDetail);
if (m_hDev == INVALID_HANDLE_VALUE)
{
AfxMessageBox( "Error: CreateFile failed\n" );
return ;
}
AfxMessageBox( "设备成功打开!" );
GetDlgItem(IDC_BTN_OPENDEV_BY_INTERFACE)->EnableWindow(FALSE);
GetDlgItem(IDC_BTN_OPENDEV_BY_NAME)->EnableWindow(FALSE);
EnableControl(TRUE);
}
}
2)用Symbolic Link方式打开设备
Driver Studio通过KUnitizedName函数生成设备的Symbolic Link,我们再利用CreateFile来打开设备,其一般形式为:
hDevice = CreateFile( "\\\\.\\OMNIPORT3" ,
GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL ,
NULL);
其中前缀“\\\\.\\”表示Createfile试图打开一个设备,然后紧跟设备名,其设备名可以利用DDK提供的“Device Tree”工具来查询,也可以查看DriverStudio向导生成的代码中KUnitizedName实例代码(位于AddDevice功能函数内),如果第一个参数为:L”TestDevice”,则设备名一般应为TestDevice0
在上一节实例中生成的驱动程序是以Interface方式打开的,现在我们重新配置此项目,在配置向导第“Step 12 of 14”中,打开设备(Open Device)选择“Symbolic Link”选项,并填写Symbolic Link的值为:Easy_USB_51_Programer_SymbolicDevice:
下载编译后驱动
下载驱动源码 (需要放到C:\Easy_USB_51_Programer_Symbolic目录)
安装驱动程序,然后就可以用Symbolic Link的方式打开设备了,而Symbolic Link的值就应该为Easy_USB_51_Programer_SymbolicDevice0,这个值在配置向导第“Step 12 of 14”中已经填好(但其后要加“0”),通过查看驱动源程序也可以得到(位于AddDevice功能函数的KUnitizedName实例代码的第一个参数),也可用第三方工具如Symbolic Link Viewer查询,下图是利用DriverStudio提供的Symbolic Link Viewer工具查询到的Symbolic Link值:
好了,现在贴出以Symbolic Link方式打开设备的代码:
void CWR_CtrlLEDDlg::OnOpendevBySymbolicLink()
{
char *sLinkName = "\\\\.\\Easy_USB_51_Programer_SymbolicDevice0" ;
m_hDev = CreateFile(sLinkName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL);
if (m_hDev == INVALID_HANDLE_VALUE)
{
AfxMessageBox( "Error: CreateFile failed" );
return ;
}
AfxMessageBox( "设备成功打开!" );
}
2、读写设备
读写设备除了可以通过WriteFile和ReadFile函数以外,还可以利用DeviceIoControl函数。WriteFile最终会调用驱动程序的IRP_MJ_READ实例代码(DriverStudio里是Write实例函数),ReadFile最终会调用驱动程序的IRP_MJ_ WRITE实例代码(DriverStudio里是Read实例函数),而DeviceIoControl最终会调用驱动程序的IRP_MJ_DEVICE_CONTROL实例代码(DriverStudio里是DeviceControl)。
DeviceIoControl的第二个参数为操作代码,在驱动程序里的定义形如IOCTL_XXXX格式,对于自定义的设备操作代码,一般用CTL_CODE宏定义,我们需要拷贝上一节驱动程序中对IOCTL_LED和IOCTL_GET_KEY两个操作代码的定义(在使用前需要包含头文件:#include "Winioctl.h")
#define IOCTL_LED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_WRITE_DATA)
#define IOCTL_GET_KEY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
下面放上利用MFC编写的一个基于对话的程序,它主要实现与上一节中测试程序相似的功能(通过端点2读写数据,通过控制端点控制扩展板EXT-BOARD-A上的LED状态,通过控制端点读取扩展板EXT-BOARD-A上的按键的状态),另外还测试了两种打开设备的方式(设备安装驱动程序后就决定了设备只能以某一种方式打开,用不正确的方式打开会报错)。
应用程序界面
(呵呵,我已经投PCB了,所以没有用手工制作的那个原始东东来展示)
编译好的应用程序
应用程序源代码
这里再次放上下位机的固件程序及其源代码,还有两种以不同方式打开设备的Windows驱动程序及源代码
下位机固件源代码
Windows驱动程序 (只能以Interface方式打开)
Windows驱动程序源代码 (只能以Interface方式打开)
Windows驱动程序 (只能以Symbolic Link方式打开)
Windows驱动程序源代码 (只能以Symbolic Link方式打开)